IDiginsightActivitiesLogOptions Interface

Configuration options for activity logging behavior and rendering control

reference
diagnostics
activity-logging
configuration
Provides configuration options to control how activities are logged, including log behavior, log levels, and payload rendering settings. Used by ActivityLifecycleLogEmitter to determine logging decisions for .NET activities.
Author

Diginsight Team

Published

January 19, 2026

IDiginsightActivitiesLogOptions Interface

The IDiginsightActivitiesLogOptions provides configuration options for controlling activity logging behavior with fine-grained control over log visibility, rendering, and formatting.

In particular, it defines how activities are logged, which activities should be shown or hidden, at what log level they should be recorded, and how their content should be rendered. This interface is consumed by the ActivityLifecycleLogEmitter to make logging decisions during activity lifecycle events.

IDiginsightActivitiesLogOptions is part of Diginsight.Diagnostics (Diginsight.Diagnostics.dll).

The primary implementation is DiginsightActivitiesOptions, which supports dynamic and volatile configuration patterns for runtime adjustments.

Table of Contents

📋 Overview

The IDiginsightActivitiesLogOptions interface controls activity logging behavior at multiple levels:

  1. Global log behavior: Default logging mode for all activities
  2. Activity-specific behavior: Override logging for specific activity names
  3. Log level assignment: Determine at which severity level activities are logged
  4. Format control: Configure how activity information is formatted (prefix vs suffix)
  5. Payload rendering: Control whether activity payloads (parameters, results) are rendered

This interface is primarily consumed by ActivityLifecycleLogEmitter during activity start and stop events to determine: - Whether the activity should be logged (LogBehavior) - At what log level (LogLevel) - How to format the output (WriteActivityActionAsPrefix) - Whether to include payload details (DisablePayloadRendering)

Key Features

  • Flexible Log Behavior Control: Configure activities to be shown, hidden, or truncated at both global and individual levels
  • Activity Name Dictionary: Map specific activity names to custom log behaviors for fine-grained control
  • Log Level Configuration: Specify the severity level for activity logging (Debug, Information, Warning, etc.)
  • Format Customization: Control whether activity actions appear as prefix or suffix in log messages
  • Payload Rendering Toggle: Enable or disable rendering of activity parameters and return values
  • Class-Aware Support: Works with IClassAwareOptions<T> for class-level configuration granularity
  • Dynamic Configuration: Supports IDynamicallyConfigurable for runtime updates
  • Volatile Configuration: Supports IVolatilelyConfigurable for temporary overrides

Log Behavior Control

The interface provides three primary log behaviors through the LogBehavior enum:

Behavior Effect Use Case
Show Activity and all children are logged normally Default behavior for visible activities
Hide Activity and all children are not logged Performance-sensitive code, high-frequency operations
Truncate Activity is logged but children are hidden Show high-level operations without internal details

The LogBehavior property sets the default behavior, while ActivityNames dictionary allows per-activity overrides.

Activity-Specific Configuration

The ActivityNames dictionary enables precise control over individual activities:

ActivityNames = new Dictionary<string, LogBehavior>
{
    ["UserService.GetUserById"] = LogBehavior.Show,
    ["DatabaseQuery"] = LogBehavior.Truncate,
    ["HighFrequencyPolling"] = LogBehavior.Hide
}

Activities not present in the dictionary inherit the global LogBehavior setting.


🔍 Additional Details

LogBehavior Enum Values

The LogBehavior enum is defined in Diginsight.Diagnostics namespace:

public enum LogBehavior
{
    Show,      // Log this activity and all children normally
    Hide,      // Do not log this activity or any children
    Truncate   // Log this activity but hide all children
}

Hierarchical Behavior: - When a parent activity has Truncate, all child activities inherit Truncate regardless of their own settings - This prevents partial visibility of deeply nested call stacks - When a parent activity has Hide, children are also not logged

Example Hierarchy:

Activity A (Show)
  ├─ Activity B (Truncate)  ← Only B is logged
  │   ├─ Activity C (Show)  ← NOT logged (parent is Truncate)
  │   └─ Activity D (Show)  ← NOT logged (parent is Truncate)
  └─ Activity E (Show)      ← Logged normally
      └─ Activity F (Show)  ← Logged normally

Activity Name Matching

Activity names in the ActivityNames dictionary are matched using exact string comparison:

Matching Rules: - Activity name is taken from Activity.OperationName - Case-sensitive matching - No wildcard or pattern matching support (exact match only) - First match wins (no priority ordering)

Example:

// These are different activity names
"MyService.GetData"     // Exact name
"MyService.GetDataAsync" // Different name (includes "Async")
"MyService.GetData()"   // Different name (includes parentheses)

Log Level Control

The LogLevel property determines the severity level at which activities are logged:

Common Log Levels: - LogLevel.Trace - Extremely detailed diagnostic information - LogLevel.Debug - Detailed debugging information (default for activities) - LogLevel.Information - General informational messages - LogLevel.Warning - Warning messages for potentially harmful situations - LogLevel.Error - Error messages for failures

Integration with Logger Configuration:

// Activities logged at Debug level
// Only visible if logger is configured to show Debug or lower
services.Configure<DiginsightActivitiesOptions>(options =>
{
    options.ActivityLogLevel = LogLevel.Debug;
});

// Logger must allow Debug level for the namespace
services.AddLogging(builder =>
{
    builder.SetMinimumLevel(LogLevel.Debug);
});

Payload Rendering Control

The DisablePayloadRendering property controls whether activity parameters and results are included in log output:

When enabled (true): - Activity start logs show only activity name - Activity end logs show only activity name and duration - Parameter values are NOT rendered - Return values are NOT rendered - Reduces log verbosity and potential PII exposure

When disabled (false, default): - Activity start logs include parameter names and values - Activity end logs include return values - Full diagnostic details available - May expose sensitive data

// Example output comparison

// DisablePayloadRendering = false (default)
START MyService.GetUser(userId: 12345, includeDetails: true)
MyService.GetUser STOP (duration: 45ms) => User { Id: 12345, Name: "John Doe" }

// DisablePayloadRendering = true
START MyService.GetUser
MyService.GetUser STOP (duration: 45ms)

⚙️ Configuration

Configuration in appsettings.json

{
  "Diginsight": {
    "DiginsightActivitiesOptions": {
      "LogBehavior": "Show",
      "ActivityLogLevel": "Debug",
      "WriteActivityActionAsPrefix": false,
      "DisablePayloadRendering": false,
      "LoggedActivityNames": {
        "UserService.GetUserById": "Show",
        "UserService.GetUserList": "Truncate",
        "HighFrequencyPoller": "Hide",
        "DatabaseQuery": "Truncate"
      }
    }
  }
}

Configuration Properties: - LogBehavior: Global default behavior - "Show", "Hide", or "Truncate" - ActivityLogLevel: Severity level - "Trace", "Debug", "Information", "Warning", "Error", "Critical" - WriteActivityActionAsPrefix: true for "START Activity", false for "Activity START" - DisablePayloadRendering: true to hide parameters/results, false to show them - LoggedActivityNames: Dictionary mapping activity names to specific behaviors

Configuration in the startup sequence

Register Diginsight diagnostics in your service collection:

// In Program.cs

using Diginsight.Diagnostics;
using Microsoft.Extensions.Logging;

var builder = WebApplication.CreateBuilder(args);

// Add Diginsight diagnostics
builder.Services.AddDiginsightDiagnostics();

// Configure activity logging options
builder.Services.Configure<DiginsightActivitiesOptions>(options =>
{
    options.LogBehavior = LogBehavior.Show;
    options.ActivityLogLevel = LogLevel.Debug;
    options.WriteActivityActionAsPrefix = false;
    options.DisablePayloadRendering = false;
    
    // Configure specific activities
    options.LoggedActivityNames["MyService.CriticalOperation"] = LogBehavior.Show;
    options.LoggedActivityNames["HighFrequencyTask"] = LogBehavior.Hide;
});

// Or bind from configuration
builder.Services.Configure<DiginsightActivitiesOptions>(
    builder.Configuration.GetSection("Diginsight:DiginsightActivitiesOptions"));

var app = builder.Build();

Class-Aware Configuration

Configure different options for specific classes or namespaces:

services.ConfigureClassAware<DiginsightActivitiesOptions>(
    "MyApp.Services.*",
    options =>
    {
        // All services: log activities at Information level
        options.ActivityLogLevel = LogLevel.Information;
        options.LogBehavior = LogBehavior.Show;
    });

services.ConfigureClassAware<DiginsightActivitiesOptions>(
    "MyApp.Repositories.*",
    options =>
    {
        // Repositories: truncate to hide low-level SQL queries
        options.LogBehavior = LogBehavior.Truncate;
        options.ActivityLogLevel = LogLevel.Debug;
    });

services.ConfigureClassAware<DiginsightActivitiesOptions>(
    "MyApp.HighFrequency.*",
    options =>
    {
        // High-frequency operations: hide to reduce noise
        options.LogBehavior = LogBehavior.Hide;
    });

Key Points: - Class-aware configuration uses pattern matching on fully qualified type names - More specific patterns override less specific ones - Supports wildcards (*) for namespace matching - Retrieved via IClassAwareOptions<DiginsightActivitiesOptions> by passing the caller type

Dynamic and Volatile Configuration

DiginsightActivitiesOptions implements both IDynamicallyConfigurable and IVolatilelyConfigurable:

// Dynamic configuration - persists across requests
public class ConfigurationController : ControllerBase
{
    private readonly IOptionsMonitor<DiginsightActivitiesOptions> _optionsMonitor;
    
    public IActionResult EnableDebugLogging(string activityName)
    {
        var options = _optionsMonitor.CurrentValue;
        
        // Update at runtime
        options.LoggedActivityNames[activityName] = LogBehavior.Show;
        options.ActivityLogLevel = LogLevel.Trace;
        
        return Ok($"Enabled debug logging for {activityName}");
    }
}

// Volatile configuration - temporary override for single request
public class MyService
{
    private readonly IVolatileConfigurationManager _volatileConfig;
    
    public async Task<T> ExecuteWithDetailedLogging<T>(Func<Task<T>> operation)
    {
        // Temporarily enable verbose logging
        using var scope = _volatileConfig.BeginScope(new DiginsightActivitiesOptions
        {
            LogBehavior = LogBehavior.Show,
            ActivityLogLevel = LogLevel.Trace,
            DisablePayloadRendering = false
        });
        
        return await operation();
    }
}

💡 Usage Examples

Basic Usage

using Diginsight.Diagnostics;
using Microsoft.Extensions.Options;

public class MyService
{
    private readonly ILogger<MyService> _logger;
    private readonly IOptionsMonitor<DiginsightActivitiesOptions> _activitiesOptions;

    public MyService(
        ILogger<MyService> logger,
        IOptionsMonitor<DiginsightActivitiesOptions> activitiesOptions)
    {
        _logger = logger;
        _activitiesOptions = activitiesOptions;
    }

    public void CheckCurrentConfiguration()
    {
        var options = (IDiginsightActivitiesLogOptions)_activitiesOptions.CurrentValue;
        
        _logger.LogInformation("Activity logging configuration:");
        _logger.LogInformation("  Global behavior: {Behavior}", options.LogBehavior);
        _logger.LogInformation("  Log level: {Level}", options.LogLevel);
        _logger.LogInformation("  Prefix format: {Prefix}", options.WriteActivityActionAsPrefix);
        _logger.LogInformation("  Disable payloads: {Disabled}", options.DisablePayloadRendering);
        
        foreach (var kvp in options.ActivityNames)
        {
            _logger.LogInformation("  Activity '{Name}': {Behavior}", 
                kvp.Key, kvp.Value);
        }
    }
}

Explanation: - Access current options via IOptionsMonitor<DiginsightActivitiesOptions> - Cast to IDiginsightActivitiesLogOptions to access the interface properties - All properties are read-only from the interface perspective - Configuration can be updated via the concrete DiginsightActivitiesOptions class

Filtering Specific Activities

// In Startup.cs or Program.cs

services.Configure<DiginsightActivitiesOptions>(options =>
{
    // Default: hide all activities
    options.LogBehavior = LogBehavior.Hide;
    
    // Explicitly show important business operations
    options.LoggedActivityNames["OrderService.CreateOrder"] = LogBehavior.Show;
    options.LoggedActivityNames["PaymentService.ProcessPayment"] = LogBehavior.Show;
    options.LoggedActivityNames["InventoryService.ReserveItems"] = LogBehavior.Show;
    
    // Show high-level operation but hide internal details
    options.LoggedActivityNames["ReportService.GenerateReport"] = LogBehavior.Truncate;
    
    // Keep sensitive operations hidden
    options.LoggedActivityNames["AuthenticationService.ValidatePassword"] = LogBehavior.Hide;
});

Key behaviors: - Activities not in the dictionary inherit the global LogBehavior (Hide) - Explicit entries override the global setting - Use Truncate to show the operation without exposing internal implementation details - Use Hide for sensitive or high-frequency operations

Example Output:

// OrderService.CreateOrder (Show)
START OrderService.CreateOrder(orderId: "ORD-12345", items: [...])
  START ValidationService.ValidateItems        ← NOT logged (parent is Show, but child would be logged)
  START InventoryService.ReserveItems          ← Logged (explicit Show)
  START PaymentService.ProcessPayment          ← Logged (explicit Show)
    START PaymentGateway.AuthorizePayment      ← NOT logged if PaymentService is Truncate
OrderService.CreateOrder STOP (duration: 234ms)

// ReportService.GenerateReport (Truncate)
START ReportService.GenerateReport(reportType: "Sales")
  START DataService.FetchData                  ← NOT logged (parent is Truncate)
  START ReportBuilder.BuildReport              ← NOT logged (parent is Truncate)
ReportService.GenerateReport STOP (duration: 1.2s)

Controlling Payload Rendering

// Scenario 1: Disable payloads globally for production security
services.Configure<DiginsightActivitiesOptions>(options =>
{
    options.LogBehavior = LogBehavior.Show;
    options.DisablePayloadRendering = true;  // No parameters or results in logs
});

// Scenario 2: Selective payload rendering using class-aware config
services.ConfigureClassAware<DiginsightActivitiesOptions>(
    "MyApp.PublicApi.*",
    options =>
    {
        // Public API: show activities but hide payloads (may contain PII)
        options.LogBehavior = LogBehavior.Show;
        options.DisablePayloadRendering = true;
    });

services.ConfigureClassAware<DiginsightActivitiesOptions>(
    "MyApp.InternalServices.*",
    options =>
    {
        // Internal services: show full details
        options.LogBehavior = LogBehavior.Show;
        options.DisablePayloadRendering = false;
    });

Important Notes: - Disabling payload rendering improves security by preventing PII exposure in logs - Reduces log volume significantly for activities with large parameters or results - Still provides timing information (duration) for performance analysis - Cannot be overridden per-activity (global or class-level only)

Using Custom Activity Logging Filter

Implement IActivityLoggingFilter for dynamic behavior decisions:

public class CustomActivityLoggingFilter : IActivityLoggingFilter
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    
    public CustomActivityLoggingFilter(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }
    
    public LogBehavior? GetLogBehavior(Activity activity)
    {
        // Dynamic decision based on HTTP header
        var httpContext = _httpContextAccessor.HttpContext;
        if (httpContext?.Request.Headers.TryGetValue("X-Enable-Verbose-Logging", out var value) == true
            && value == "true")
        {
            return LogBehavior.Show;
        }
        
        // Dynamic decision based on activity duration
        if (activity.Duration.TotalMilliseconds > 1000)
        {
            // Always log slow operations
            return LogBehavior.Show;
        }
        
        // Respect configuration for other cases
        return null;
    }
}

// Register the filter
services.AddSingleton<IActivityLoggingFilter, CustomActivityLoggingFilter>();

🚀 Advanced Usage

Class-Level Configuration

Combine class-aware configuration with namespace patterns for granular control:

public static class ActivityLoggingConfiguration
{
    public static IServiceCollection ConfigureActivityLogging(
        this IServiceCollection services, 
        IConfiguration configuration)
    {
        // Global default
        services.Configure<DiginsightActivitiesOptions>(options =>
        {
            options.LogBehavior = LogBehavior.Hide;
            options.ActivityLogLevel = LogLevel.Information;
        });
        
        // Controllers: log API entry points
        services.ConfigureClassAware<DiginsightActivitiesOptions>(
            "*.Controllers.*Controller",
            options =>
            {
                options.LogBehavior = LogBehavior.Show;
                options.ActivityLogLevel = LogLevel.Information;
                options.WriteActivityActionAsPrefix = true;
                options.DisablePayloadRendering = true;  // Protect PII in API logs
            });
        
        // Business services: detailed logging
        services.ConfigureClassAware<DiginsightActivitiesOptions>(
            "*.Services.*Service",
            options =>
            {
                options.LogBehavior = LogBehavior.Show;
                options.ActivityLogLevel = LogLevel.Debug;
                options.DisablePayloadRendering = false;
            });
        
        // Repositories: show high-level, hide queries
        services.ConfigureClassAware<DiginsightActivitiesOptions>(
            "*.Repositories.*Repository",
            options =>
            {
                options.LogBehavior = LogBehavior.Truncate;
                options.ActivityLogLevel = LogLevel.Debug;
            });
        
        // Background workers: minimal logging
        services.ConfigureClassAware<DiginsightActivitiesOptions>(
            "*.Workers.*Worker",
            options =>
            {
                options.LogBehavior = LogBehavior.Hide;
            });
        
        return services;
    }
}

Advanced Capabilities: - Layer-based configuration (controllers, services, repositories, workers) - Different log levels and behaviors per architectural layer - Selective payload rendering based on security requirements - Wildcard patterns for flexible namespace matching

Runtime Configuration Updates

public class DiagnosticsController : ControllerBase
{
    private readonly IOptionsMonitor<DiginsightActivitiesOptions> _optionsMonitor;
    private readonly ILogger<DiagnosticsController> _logger;
    
    public DiagnosticsController(
        IOptionsMonitor<DiginsightActivitiesOptions> optionsMonitor,
        ILogger<DiagnosticsController> logger)
    {
        _optionsMonitor = optionsMonitor;
        _logger = logger;
    }
    
    [HttpPost("enable-verbose/{activityName}")]
    public IActionResult EnableVerboseLogging(string activityName)
    {
        try
        {
            var options = _optionsMonitor.CurrentValue;
            
            // Runtime update - persists until next configuration reload
            options.LoggedActivityNames[activityName] = LogBehavior.Show;
            
            _logger.LogInformation(
                "Enabled verbose logging for activity: {ActivityName}", 
                activityName);
            
            return Ok(new { 
                message = $"Verbose logging enabled for {activityName}",
                currentBehavior = LogBehavior.Show
            });
        }
        catch (InvalidOperationException ex)
        {
            // Options instance might be frozen
            _logger.LogError(ex, "Failed to update activity logging configuration");
            return StatusCode(500, "Configuration update failed");
        }
    }
    
    [HttpPost("disable-payloads")]
    public IActionResult DisablePayloadRendering()
    {
        var options = _optionsMonitor.CurrentValue;
        options.DisablePayloadRendering = true;
        
        _logger.LogWarning("Payload rendering disabled globally");
        
        return Ok("Payload rendering disabled");
    }
    
    [HttpGet("current-config")]
    public IActionResult GetCurrentConfiguration()
    {
        var options = (IDiginsightActivitiesLogOptions)_optionsMonitor.CurrentValue;
        
        return Ok(new
        {
            logBehavior = options.LogBehavior.ToString(),
            logLevel = options.LogLevel.ToString(),
            writeActivityActionAsPrefix = options.WriteActivityActionAsPrefix,
            disablePayloadRendering = options.DisablePayloadRendering,
            activityOverrides = options.ActivityNames.ToDictionary(
                kvp => kvp.Key, 
                kvp => kvp.Value.ToString())
        });
    }
}

Hierarchical Log Behavior

Understanding and leveraging behavior inheritance:

// Configure parent activity to truncate
services.Configure<DiginsightActivitiesOptions>(options =>
{
    options.LogBehavior = LogBehavior.Show;  // Global default
    
    // Parent operation: show but don't show internals
    options.LoggedActivityNames["ComplexOperation"] = LogBehavior.Truncate;
    
    // Child operations: these settings are IGNORED if parent is Truncate
    options.LoggedActivityNames["ComplexOperation.Step1"] = LogBehavior.Show;
    options.LoggedActivityNames["ComplexOperation.Step2"] = LogBehavior.Show;
});

// Example activities
public class ComplexService
{
    [Activity("ComplexOperation")]  // Will be logged (Truncate)
    public async Task ComplexOperation()
    {
        await Step1();  // Will NOT be logged (parent is Truncate)
        await Step2();  // Will NOT be logged (parent is Truncate)
    }
    
    [Activity("ComplexOperation.Step1")]
    private async Task Step1()
    {
        // Internal implementation
    }
    
    [Activity("ComplexOperation.Step2")]
    private async Task Step2()
    {
        // Internal implementation
    }
}

Pattern Options:

Parent Behavior Child Behavior Effective Child Behavior Use Case
Show Show Show Full visibility of operation hierarchy
Show Hide Hide Selectively hide specific children
Show Truncate Truncate Hide grandchildren but show direct children
Truncate Show Truncate (inherited) Hide all descendants regardless of settings
Truncate Hide Truncate (inherited) Same as above
Hide Show Hide (not logged) Entire subtree is hidden

🔧 Troubleshooting

Common Issues

1. Activities Not Being Logged

Activities may be hidden due to multiple configuration layers.

// Problem: Activity not appearing in logs
// Possible causes:
// 1. Global LogBehavior is Hide
// 2. Parent activity is Truncate or Hide
// 3. Logger minimum level too high
// 4. Activity name doesn't match configuration

// Solution: Enable diagnostic logging
services.Configure<DiginsightActivitiesOptions>(options =>
{
    options.LogBehavior = LogBehavior.Show;  // Ensure default is Show
    options.ActivityLogLevel = LogLevel.Debug;
    
    // Explicitly enable specific activity
    options.LoggedActivityNames["MyService.MyMethod"] = LogBehavior.Show;
});

// Ensure logger level is appropriate
services.AddLogging(builder =>
{
    builder.SetMinimumLevel(LogLevel.Debug);
    builder.AddFilter("Diginsight.Diagnostics", LogLevel.Trace);  // Verbose Diginsight logs
});

2. Activity Names Not Matching

Activity name in configuration must match Activity.OperationName exactly.

Ensure that: - Activity name is case-sensitive exact match - No extra spaces or special characters - Activity source is registered and started - Activity.Current is not null

Debugging:

public class MyService
{
    public void MyMethod()
    {
        var activity = Activity.Current;
        if (activity != null)
        {
            _logger.LogInformation("Current activity name: '{Name}'", activity.OperationName);
            // Check this name matches configuration exactly
        }
        else
        {
            _logger.LogWarning("No current activity - check ActivitySource registration");
        }
    }
}

3. Payload Still Being Rendered Despite DisablePayloadRendering = true

If payloads are still appearing in logs:

  • Check that configuration is loaded correctly:

    var options = _optionsMonitor.CurrentValue;
    _logger.LogInformation("DisablePayloadRendering: {Value}", 
        options.DisablePayloadRendering);
  • Verify no class-aware override:

    // Class-aware config might override global setting
    var classOptions = _classAwareOptions.Get(typeof(MyService));
  • Check for frozen options - updates to frozen instances throw exceptions

4. Configuration Changes Not Taking Effect

Configuration updates may not reflect immediately.

  • Symptom: Updated appsettings.json but behavior unchanged
  • Cause: Options snapshot cached or instance frozen
  • Solution: Use IOptionsMonitor<T> and access CurrentValue for live updates
// ✓ Recommended: IOptionsMonitor for dynamic updates
public class MyService
{
    private readonly IOptionsMonitor<DiginsightActivitiesOptions> _optionsMonitor;
    
    public MyService(IOptionsMonitor<DiginsightActivitiesOptions> optionsMonitor)
    {
        _optionsMonitor = optionsMonitor;
    }
    
    public void DoWork()
    {
        // Always gets current configuration
        var options = _optionsMonitor.CurrentValue;
    }
}

// ✗ Avoid: IOptions<T> caches at construction time
public class MyService
{
    private readonly DiginsightActivitiesOptions _options;
    
    public MyService(IOptions<DiginsightActivitiesOptions> options)
    {
        _options = options.Value;  // Cached - won't see updates
    }
}

Debugging

Enable detailed logging to troubleshoot activity logging issues:

// In appsettings.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Diginsight.Diagnostics": "Trace",  // Verbose Diginsight internal logs
      "Diginsight.Diagnostics.ActivityLifecycleLogEmitter": "Trace"  // Activity logging decisions
    }
  }
}

// Or in code
services.Configure<LoggerFilterOptions>(options =>
{
    options.AddFilter("Diginsight.Diagnostics", LogLevel.Trace);
});

Debugging Techniques: - Enable Trace level logging for Diginsight.Diagnostics namespace - Log current Activity.Current.OperationName to verify activity names - Check IOptionsMonitor<DiginsightActivitiesOptions>.CurrentValue to inspect configuration - Verify ActivitySource is registered and activities are started - Use IActivityLoggingFilter with breakpoints to debug behavior decisions

Telemetry and Monitoring:

// Create diagnostic middleware to monitor configuration
public class ActivityLoggingDiagnosticMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ActivityLoggingDiagnosticMiddleware> _logger;
    
    public ActivityLoggingDiagnosticMiddleware(
        RequestDelegate next, 
        ILogger<ActivityLoggingDiagnosticMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }
    
    public async Task InvokeAsync(
        HttpContext context, 
        IOptionsMonitor<DiginsightActivitiesOptions> optionsMonitor)
    {
        if (context.Request.Path.StartsWithSegments("/_diagnostics/activity-config"))
        {
            var options = (IDiginsightActivitiesLogOptions)optionsMonitor.CurrentValue;
            
            context.Response.ContentType = "application/json";
            await context.Response.WriteAsJsonAsync(new
            {
                logBehavior = options.LogBehavior.ToString(),
                logLevel = options.LogLevel.ToString(),
                writeActivityActionAsPrefix = options.WriteActivityActionAsPrefix,
                disablePayloadRendering = options.DisablePayloadRendering,
                activityOverrides = options.ActivityNames
            });
            return;
        }
        
        await _next(context);
    }
}

// Register middleware
app.UseMiddleware<ActivityLoggingDiagnosticMiddleware>();

// Access: https://yourapp/_diagnostics/activity-config

Performance Considerations

Activity Logging Overhead: - Activity creation: ~10-50 microseconds per activity - Log output: depends on logger backend and payload size - Payload rendering: can add significant overhead for large objects

Optimization Strategies:

// Strategy 1: Use Hide for high-frequency operations
services.Configure<DiginsightActivitiesOptions>(options =>
{
    options.LoggedActivityNames["PollingService.Poll"] = LogBehavior.Hide;
    options.LoggedActivityNames["CacheCheck"] = LogBehavior.Hide;
    options.LoggedActivityNames["HealthCheck"] = LogBehavior.Hide;
});

// Strategy 2: Disable payload rendering in production
services.Configure<DiginsightActivitiesOptions>(options =>
{
    options.DisablePayloadRendering = true;  // Significant performance improvement
});

// Strategy 3: Use Truncate for deep call stacks
services.Configure<DiginsightActivitiesOptions>(options =>
{
    options.LoggedActivityNames["HttpClientCall"] = LogBehavior.Truncate;
    // Prevents logging of all HTTP pipeline activities
});

// Strategy 4: Increase log level to reduce noise
services.Configure<DiginsightActivitiesOptions>(options =>
{
    options.ActivityLogLevel = LogLevel.Information;  // From Debug
    // Only logs if logger allows Information level
});

Monitoring Recommendations: - Monitor log volume (MB/hour) to detect excessive logging - Track activity creation rate (activities/second) - Measure P95/P99 latency with and without activity logging - Alert on sudden increases in log volume - Use sampling for high-frequency operations


📚 Reference

Interface Definition

Namespace: Diginsight.Diagnostics
Assembly: Diginsight.Diagnostics.dll

public interface IDiginsightActivitiesLogOptions
{
    IReadOnlyDictionary<string, LogBehavior> ActivityNames { get; }
    LogBehavior LogBehavior { get; }
    LogLevel LogLevel { get; }
    bool WriteActivityActionAsPrefix { get; }
    bool DisablePayloadRendering { get; }
}

Properties

Property Type Description Default
ActivityNames IReadOnlyDictionary<string, LogBehavior> Dictionary mapping activity names to specific log behaviors. Enables per-activity override of global LogBehavior. Empty dictionary
LogBehavior LogBehavior Global default log behavior for activities not specified in ActivityNames dictionary. LogBehavior.Hide
LogLevel LogLevel The severity level at which activities are logged (Trace, Debug, Information, Warning, Error, Critical). LogLevel.Debug
WriteActivityActionAsPrefix bool When true, formats activity logs as "START Activity" and "STOP Activity". When false, formats as "Activity START" and "Activity STOP". false
DisablePayloadRendering bool When true, suppresses rendering of activity parameters and return values in log output. When false, includes full payload details. false

Implementing Classes

  • DiginsightActivitiesOptions: Primary concrete implementation
    • Implements IDiginsightActivitiesLogOptions
    • Also implements IDiginsightActivitiesOptions, IMetricRecordingOptions
    • Supports IDynamicallyConfigurable and IVolatilelyConfigurable
    • Mutable properties with frozen state support
    • Located in Diginsight.Diagnostics namespace

Consumption Points: - ActivityLifecycleLogEmitter: Consumes this interface to determine logging behavior during activity lifecycle events (start/stop) - OptionsBasedActivityLoggingFilter: Uses this interface to implement IActivityLoggingFilter - Class-Aware Options System: Retrieved via IClassAwareOptions<DiginsightActivitiesOptions>.Get(Type callerType)


💡 Best Practices

Configuration Organization

Organize by Architectural Layer:

public static class ActivityLoggingSetup
{
    public static IServiceCollection AddLayeredActivityLogging(
        this IServiceCollection services)
    {
        // Layer 1: Presentation (hide payloads for security)
        services.ConfigureClassAware<DiginsightActivitiesOptions>(
            "*.Controllers.*",
            options =>
            {
                options.LogBehavior = LogBehavior.Show;
                options.ActivityLogLevel = LogLevel.Information;
                options.DisablePayloadRendering = true;  // PII protection
            });
        
        // Layer 2: Application/Services (full details)
        services.ConfigureClassAware<DiginsightActivitiesOptions>(
            "*.Services.*",
            options =>
            {
                options.LogBehavior = LogBehavior.Show;
                options.ActivityLogLevel = LogLevel.Debug;
                options.DisablePayloadRendering = false;
            });
        
        // Layer 3: Infrastructure (truncate to hide low-level details)
        services.ConfigureClassAware<DiginsightActivitiesOptions>(
            "*.Repositories.*",
            options =>
            {
                options.LogBehavior = LogBehavior.Truncate;
            });
        
        return services;
    }
}

Guidelines: - Use environment-specific configuration (appsettings.Production.json) - Apply least-privilege logging (hide by default, show explicitly) - Document activity naming conventions in team standards - Version control configuration files with code - Review logging configuration during security audits

Performance Optimization

Minimize Logging Overhead:

// ✓ Recommended: Hide high-frequency operations
services.Configure<DiginsightActivitiesOptions>(options =>
{
    options.LoggedActivityNames["PollingWorker.Poll"] = LogBehavior.Hide;
    options.LoggedActivityNames["CacheService.Get"] = LogBehavior.Hide;
    options.LoggedActivityNames["MetricsCollector.Collect"] = LogBehavior.Hide;
});

// ✓ Recommended: Disable payloads when not needed
services.Configure<DiginsightActivitiesOptions>(options =>
{
    options.DisablePayloadRendering = true;  // Production default
});

// ✓ Recommended: Use Truncate for deep hierarchies
services.Configure<DiginsightActivitiesOptions>(options =>
{
    options.LoggedActivityNames["ExternalApiClient.Call"] = LogBehavior.Truncate;
    // Prevents logging of entire HTTP pipeline
});

// ✗ Avoid: Logging everything at Trace level
services.Configure<DiginsightActivitiesOptions>(options =>
{
    options.LogBehavior = LogBehavior.Show;
    options.ActivityLogLevel = LogLevel.Trace;
    options.DisablePayloadRendering = false;
    // Massive log volume and performance impact
});

When to Use Each Behavior: - Show: Business operations, API endpoints, critical paths - Hide: High-frequency operations, background tasks, health checks - Truncate: External service calls, database operations, deep hierarchies

Key Recommendations: 1. Start with LogBehavior.Hide as default, enable explicitly 2. Always disable payload rendering in production for APIs 3. Use Truncate for operations with deep call stacks 4. Monitor log volume and adjust configuration accordingly 5. Profile performance impact before deploying verbose logging

Security Considerations

Protect Sensitive Data:

// Security pattern: Environment-based configuration
public static IServiceCollection ConfigureSecureActivityLogging(
    this IServiceCollection services,
    IWebHostEnvironment environment)
{
    services.Configure<DiginsightActivitiesOptions>(options =>
    {
        if (environment.IsProduction())
        {
            // Production: hide payloads to prevent PII exposure
            options.DisablePayloadRendering = true;
            
            // Hide authentication/authorization activities
            options.LoggedActivityNames["AuthenticationService.Authenticate"] = LogBehavior.Hide;
            options.LoggedActivityNames["AuthorizationService.Authorize"] = LogBehavior.Hide;
            options.LoggedActivityNames["TokenService.GenerateToken"] = LogBehavior.Hide;
        }
        else
        {
            // Development: full details for debugging
            options.DisablePayloadRendering = false;
            options.LogBehavior = LogBehavior.Show;
        }
    });
    
    return services;
}

// Custom filter for dynamic PII protection
public class PiiProtectionFilter : IActivityLoggingFilter
{
    public LogBehavior? GetLogBehavior(Activity activity)
    {
        // Hide activities that may contain PII
        if (activity.OperationName.Contains("Password", StringComparison.OrdinalIgnoreCase) ||
            activity.OperationName.Contains("CreditCard", StringComparison.OrdinalIgnoreCase) ||
            activity.OperationName.Contains("SSN", StringComparison.OrdinalIgnoreCase))
        {
            return LogBehavior.Hide;
        }
        
        return null;  // Respect configuration
    }
}

When to Hide Activities: - Authentication and authorization operations - Payment processing - PII handling (email, phone, address) - Cryptographic operations - Token generation and validation

When NOT to Show Payloads: - User credentials (passwords, tokens) - Financial data (credit cards, account numbers) - Personal identifiable information - API keys and secrets - Health/medical data


📖 Appendices

Appendix A: LogBehavior Decision Flow

The ActivityLifecycleLogEmitter determines logging behavior using the following algorithm:

flowchart TD
    Start([Activity Started]) --> GetCallerType[Get Caller Type from Activity]
    GetCallerType --> GetOptions[Get IDiginsightActivitiesLogOptions<br/>for Caller Type]
    GetOptions --> CheckFilter{IActivityLoggingFilter<br/>available?}
    
    CheckFilter -->|Yes| CallFilter[Call Filter.GetLogBehavior]
    CallFilter --> FilterReturns{Filter returns<br/>value?}
    FilterReturns -->|Yes| UseFilterBehavior[Use Filter Behavior]
    FilterReturns -->|No| CheckActivityNames
    
    CheckFilter -->|No| CheckActivityNames
    
    CheckActivityNames{Activity name in<br/>ActivityNames?}
    CheckActivityNames -->|Yes| UseActivityBehavior[Use ActivityNames[name]]
    CheckActivityNames -->|No| UseDefaultBehavior[Use LogBehavior property]
    
    UseFilterBehavior --> CheckParent
    UseActivityBehavior --> CheckParent
    UseDefaultBehavior --> CheckParent
    
    CheckParent{Parent activity<br/>is Truncate?}
    CheckParent -->|Yes| ForceTruncate[Force Behavior = Truncate]
    CheckParent -->|No| KeepBehavior[Keep Computed Behavior]
    
    ForceTruncate --> SetBehavior[Set Activity.LogBehavior]
    KeepBehavior --> SetBehavior
    
    SetBehavior --> CheckShow{Behavior == Show?}
    CheckShow -->|Yes| LogActivity[Log Activity Start/Stop]
    CheckShow -->|No| SkipActivity[Skip Logging]
    
    LogActivity --> End([Activity Logged])
    SkipActivity --> End

Key Decision Points: 1. Caller Type Extraction: Activity’s CallerType custom property determines which class-aware configuration to use 2. Filter Priority: IActivityLoggingFilter has highest priority if it returns a non-null value 3. Activity-Specific Override: ActivityNames dictionary overrides global LogBehavior 4. Parent Truncate Inheritance: If parent is Truncate, child inherits Truncate regardless of other settings 5. Final Behavior Check: Only Show results in logging; Hide and Truncate skip logging

Appendix B: Integration with ActivityLifecycleLogEmitter

The ActivityLifecycleLogEmitter consumes IDiginsightActivitiesLogOptions at two key points:

1. Activity Start Event:

// Simplified pseudo-code from ActivityLifecycleLogEmitter.cs
void ActivityStarted(Activity activity)
{
    // Step 1: Get options for caller type
    Type callerType = activity.GetCallerType();
    IDiginsightActivitiesLogOptions options = activitiesOptionsMonitor.Get(callerType);
    
    // Step 2: Determine log behavior
    LogBehavior behavior = activityLoggingFilter?.GetLogBehavior(activity) 
                          ?? options.ActivityNames.GetValueOrDefault(activity.OperationName, options.LogBehavior);
    
    // Step 3: Check parent truncate inheritance
    if (activity.Parent?.GetLogBehavior() == LogBehavior.Truncate)
        behavior = LogBehavior.Truncate;
    
    // Step 4: Store behavior on activity
    activity.SetLogBehavior(behavior);
    
    // Step 5: Log if behavior is Show
    if (behavior == LogBehavior.Show)
    {
        LogLevel logLevel = options.LogLevel;
        bool writeAsPrefix = options.WriteActivityActionAsPrefix;
        bool disablePayload = options.DisablePayloadRendering;
        
        string format = writeAsPrefix ? "START {ActivityName}" : "{ActivityName} START";
        
        if (!disablePayload)
        {
            // Render parameters
            LogActivityStartWithPayload(activity, format, logLevel);
        }
        else
        {
            // Log without parameters
            LogActivityStart(activity, format, logLevel);
        }
    }
}

2. Activity Stop Event:

// Simplified pseudo-code from ActivityLifecycleLogEmitter.cs
void ActivityStopped(Activity activity)
{
    // Step 1: Retrieve stored log behavior
    LogBehavior behavior = activity.GetLogBehavior();
    
    if (behavior != LogBehavior.Show)
        return;  // Skip logging
    
    // Step 2: Get options again (may have changed)
    Type callerType = activity.GetCallerType();
    IDiginsightActivitiesLogOptions options = activitiesOptionsMonitor.Get(callerType);
    
    // Step 3: Log activity stop
    LogLevel logLevel = options.LogLevel;
    bool writeAsPrefix = options.WriteActivityActionAsPrefix;
    bool disablePayload = options.DisablePayloadRendering;
    
    string format = writeAsPrefix ? "STOP {ActivityName} (duration: {Duration}ms)" 
                                  : "{ActivityName} STOP (duration: {Duration}ms)";
    
    if (!disablePayload && activity.GetCustomProperty("Result") is object result)
    {
        // Render return value
        LogActivityStopWithResult(activity, format, logLevel, result);
    }
    else
    {
        // Log without result
        LogActivityStop(activity, format, logLevel, activity.Duration.TotalMilliseconds);
    }
}

Integration Points: - activitiesOptionsMonitor: IClassAwareOptions<DiginsightActivitiesOptions> injected into ActivityLifecycleLogEmitter - activitiesOptionsMonitor.Get(Type): Retrieves class-aware configuration for specific caller type - activityLoggingFilter: Optional IActivityLoggingFilter for dynamic behavior decisions - Activity custom properties: Stores LogBehavior between start and stop events

Configuration Flow:

appsettings.json
  ↓
DiginsightActivitiesOptions (IOptions)
  ↓
IClassAwareOptions<DiginsightActivitiesOptions>
  ↓
ActivityLifecycleLogEmitter.activitiesOptionsMonitor.Get(callerType)
  ↓
IDiginsightActivitiesLogOptions (interface cast)
  ↓
Extract properties (LogBehavior, LogLevel, etc.)
  ↓
Make logging decision

This integration ensures that all activity logging decisions respect the configured options while supporting dynamic updates and class-aware granularity.


End of Reference Documentation

Back to top